/**
 * 雪花算法 ID 生成器
 * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
 */
export class SnowflakeId {
    private static readonly twepoch = 1288834974657n;
    private static readonly workerIdBits = 5n;
    private static readonly datacenterIdBits = 5n;
    private static readonly sequenceBits = 12n;
    
    private static readonly maxWorkerId = -1n ^ (-1n << SnowflakeId.workerIdBits);
    private static readonly maxDatacenterId = -1n ^ (-1n << SnowflakeId.datacenterIdBits);
    private static readonly sequenceMask = -1n ^ (-1n << SnowflakeId.sequenceBits);
    
    private static readonly workerIdShift = SnowflakeId.sequenceBits;
    private static readonly datacenterIdShift = SnowflakeId.sequenceBits + SnowflakeId.workerIdBits;
    private static readonly timestampLeftShift = SnowflakeId.sequenceBits + SnowflakeId.workerIdBits + SnowflakeId.datacenterIdBits;

    private sequence = 0n;
    private lastTimestamp = -1n;
    private readonly workerId: bigint;
    private readonly datacenterId: bigint;

    constructor(workerId = 1n, datacenterId = 1n) {
        if (workerId > SnowflakeId.maxWorkerId || workerId < 0) {
            throw new Error(`worker Id can't be greater than ${SnowflakeId.maxWorkerId} or less than 0`);
        }
        if (datacenterId > SnowflakeId.maxDatacenterId || datacenterId < 0) {
            throw new Error(`datacenter Id can't be greater than ${SnowflakeId.maxDatacenterId} or less than 0`);
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public generate(): string {
        let timestamp = this.timeGen();
        if (timestamp < this.lastTimestamp) {
            throw new Error('Invalid system clock!');
        }

        if (this.lastTimestamp === timestamp) {
            this.sequence = (this.sequence + 1n) & SnowflakeId.sequenceMask;
            if (this.sequence === 0n) {
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            this.sequence = 0n;
        }

        this.lastTimestamp = timestamp;
        const id = ((timestamp - SnowflakeId.twepoch) << SnowflakeId.timestampLeftShift) |
                  (this.datacenterId << SnowflakeId.datacenterIdShift) |
                  (this.workerId << SnowflakeId.workerIdShift) |
                  this.sequence;
        
        return id.toString();
    }

    private tilNextMillis(lastTimestamp: bigint): bigint {
        let timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private timeGen(): bigint {
        return BigInt(Date.now());
    }
}
